package models.repository;

import models.entities.*;
import models.exceptions.*;
import models.helpers.DBSession;
import models.helpers.ImageHelper;
import models.helpers.StringHelper;
import models.repository.common.ProductRepository;
import mybatis.mappers.CategoryMapper;
import mybatis.mappers.ProductMapper;
import org.apache.ibatis.session.SqlSession;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
@Deprecated
public class ProductRepositorySQL implements ProductRepository {

    public ProductEntity getProduct(int id) throws GetEntityListException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            return productMapper.getProduct(id);
        } catch (Exception ex) {
            if (dbSession != null)
                dbSession.rollback();
            throw new GetEntityListException(MessageFormat.format("Cannot get product id: {0}; original message: {1}", id, ex.getMessage()));
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }

    public PaginatedProductList<ProductEntity> getProductList(Integer categoryId, Integer brandId, Integer pageNumber, Integer pageSize, int brandPage) throws GetEntityListException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            pageNumber = (pageNumber == null || pageNumber < 1) ? 1 : pageNumber;
            pageSize = (pageSize == null || pageSize < 1) ? 10 : pageSize;
            CategoryMapper categoryMapper = dbSession.getMapper(CategoryMapper.class);
            CategoryEntity category = categoryMapper.getCategory(categoryId);
            List<ProductEntity> products = productMapper.getProductList(categoryId, category.rangeStart, category.rangeEnd, brandId, pageNumber, pageSize, (pageNumber - 1) * pageSize);
            int brandIndex = (brandPage - 1) * 5;
            List<BrandEntity> brands = productMapper.getBrandList(categoryId, category.rangeStart, category.rangeEnd, brandPage, brandIndex);
            int totalCount = productMapper.getProductsTotalCount(categoryId, category.rangeStart, category.rangeEnd, brandId);
            return new PaginatedProductList<ProductEntity>(products, pageNumber, pageSize, totalCount, brands);
        } catch (Exception ex) {
            if (dbSession != null)
                dbSession.rollback();
            throw new GetEntityListException(MessageFormat.format("Cannot get product list from category id: {0}; original message: {1}", categoryId, ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }

    }

    //TODO: refactor parameters
    public PaginatedProductList<ProductEntity> getRecentProducts(Integer pageNumber, Integer pageSize, Integer brandId) throws GetEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            pageNumber = (pageNumber == null || pageNumber < 1) ? 1 : pageNumber;
            pageSize = (pageSize == null || pageSize < 1) ? 10 : pageSize;
            //returns products were added in last two weeks
            List<ProductEntity> productList = productMapper.getRecentProducts(pageNumber, pageSize, (pageNumber - 1) * pageSize, brandId);
            List<BrandEntity> brands = productMapper.getRecentProductsBrandList();
            int totalCount = productMapper.getRecentProductsTotalCount(brandId);
            return new PaginatedProductList<ProductEntity>(productList, pageNumber, pageSize, totalCount, brands);

        } catch (Exception ex) {
            throw new GetEntityException(MessageFormat.format("Cannot get recent products {0}", ex.getMessage()));
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }

    //TODO: Refactor parameters
    public InputStream getProductImage(int id, int number, int width, int height, String dataPath, String type) throws GetEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            PictureEntity pictureEntity = productMapper.getProductImage(id, number - 1);
            if (pictureEntity == null)
                return ImageHelper.getDefaultProductImage(dataPath, width, height, type);
            return ImageHelper.getImage(dataPath, pictureEntity, width, height, "products", type);
        } catch (Exception ex) {
            if (dbSession != null)
                dbSession.rollback();
            throw new GetEntityException(MessageFormat.format("Cannot get image for product id: {0}; original message: {1}", id, ex.getMessage()));
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }

    public int createCategory(CategoryEntity category) throws CreateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            CategoryMapper categoryMapper = dbSession.getMapper(CategoryMapper.class);
            categoryMapper.create(category);
            dbSession.commit();
        } catch (Exception ex) {
            throw new CreateEntityException(MessageFormat.format("Cannot create category '{0}'; Original message: {1}", category.title, ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
        return category.id;
    }

    public CategoryEntity getCategories(int parentId) throws IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            CategoryMapper categoryMapper = dbSession.getMapper(CategoryMapper.class);
            List<CategoryEntity> categories = categoryMapper.getById(parentId);
            CategoryEntity categoryRoot = categories.get(0);
            categoryRoot.parents = categoryMapper.getCategoryParents(categoryRoot);
            categoryRoot.aliases = categoryMapper.getCategoryAliases(parentId);
            makeTree(categoryRoot, categories);
            return categoryRoot;
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    //TODO: Check categoryRoot parameter
    private void makeTree(CategoryEntity categoryRoot, List<CategoryEntity> categories) {
        Hashtable<Integer, CategoryEntity> hashtable = new Hashtable<Integer, CategoryEntity>();
        for (CategoryEntity category : categories) {
            hashtable.put(category.id, category);
        }
        for (CategoryEntity category : categories) {
            CategoryEntity categoryEntity = hashtable.get(category.parentCategoryId);
            if (categoryEntity != null) {
                categoryEntity.categories.add(category);
            }
        }
    }

    //TODO: Check if it is used. If no - delete
    private CategoryEntity makeCategoryTree(Iterator<CategoryEntity> iterator, CategoryEntity categoryRoot) {
        if (!iterator.hasNext())
            return null;
        CategoryEntity nextCategory = iterator.next();
        while (true) {
            if (nextCategory.level > categoryRoot.level) {
                categoryRoot.categories.add(nextCategory);
                nextCategory = makeCategoryTree(iterator, nextCategory);
                if (nextCategory == null)
                    return null;
            } else {
                return nextCategory;
            }
        }
    }

    public void addPictureToBrand(PictureEntity image, String applicationPath) throws CreateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            image.filePath = ImageHelper.saveImage(applicationPath, "brands", image.format, image.pictureData, image.md5);
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            productMapper.addPictureToBrand(image);
            dbSession.commit();
        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw new CreateEntityException(MessageFormat.format("Cannot add picture to Brand id {0}; Original message: {1}", image.parentId, ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public void addPictureToProduct(PictureEntity image, String applicationPath) throws CreateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            addPicture(image, applicationPath, dbSession);
            dbSession.commit();
        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw new CreateEntityException(MessageFormat.format("Cannot add picture to product id {0}; Original message {1}", image.parentId, ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    private void addPicture(PictureEntity image, String applicationPath, SqlSession dbSession) throws IOException {
        image.filePath = ImageHelper.saveImage(applicationPath, "productimages", image.format, image.pictureData, image.md5);
        ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
        productMapper.addPicture(image);
    }

    public void addExistingImageToProduct(Integer productId, int imageId) throws UpdateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            productMapper.addExistingPictureToProduct(productId, imageId);
            dbSession.commit();
        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw new UpdateEntityException(MessageFormat.format("Cannot add picture to product id {0}; Original message {1}", productId, ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public ProductEntity getByTitle(String title) throws IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            return productMapper.getByTitle(title.toUpperCase());

        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public List<PictureEntity> getAllProductImages(Integer productId) throws IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            return productMapper.getAllProductImages(productId);
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }

    public void deleteImage(PictureEntity picture) throws IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            productMapper.deleteImage(picture.id);
            dbSession.commit();
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }

    public void addCategoryAlias(CategoryAliasEntity categoryAlias) throws CreateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            CategoryMapper categoryMapper = dbSession.getMapper(CategoryMapper.class);
            categoryMapper.addCategoryAlias(categoryAlias);
            dbSession.commit();
        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw new CreateEntityException(MessageFormat.format("Cannot create categoryAlias {0}; {1}", categoryAlias.alias, ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public void deleteCategoryAlias(int aliasId) throws DeleteEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            CategoryMapper categoryMapper = dbSession.getMapper(CategoryMapper.class);
            categoryMapper.deleteCategoryAlias(aliasId);
            dbSession.commit();
        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw new DeleteEntityException(MessageFormat.format("Cannot delete category alias: {0}", ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public void addProductSource(ProductSourceEntity productSource) throws CreateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            productMapper.addProductSource(productSource);
            dbSession.commit();
        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw new CreateEntityException(MessageFormat.format("Cannot create productsource {0}", ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public List<ProductSourceEntity> getProductSources(Integer id) throws GetEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            return productMapper.getProductSources(id);
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public void updateProductSource(ProductSourceEntity productSource) throws UpdateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            productMapper.updateProductSource(productSource);
            dbSession.commit();
        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw new UpdateEntityException(ex.getMessage());
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public void update(ProductEntity productEntity) throws UpdateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            productMapper.update(productEntity);
            dbSession.commit();
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public ProductSourceEntity getProductSource(ProductSourceEntity source) throws GetEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            return productMapper.getProductSource(source);
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public void putInCategory(ProductEntity productEntity) throws CreateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            productMapper.putInCategory(productEntity);
            dbSession.commit();
        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw new CreateEntityException(MessageFormat.format("Cannot create link for product-category {0}-{1}; Original message: {2}", productEntity.id, productEntity.categoryId, ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public boolean productHasPicture(int productId) throws Exception {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            String pictureId = productMapper.productHasPicture(productId);
            return pictureId != null;
        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw ex;
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public boolean isBrandExists(String brandName) throws Exception {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            Integer id = productMapper.isBrandExists(brandName);
            return id != null;

        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw ex;
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public BrandEntity getBrand(String brandName) throws GetEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            return productMapper.getBrand(brandName);

        } catch (Exception ex) {
            if (dbSession != null) {
                dbSession.rollback();
            }
            throw new GetEntityException(MessageFormat.format("Cannot get brand '{0}' from database; Original message: {1}", brandName, ex.getMessage()));
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public InputStream getBrandImage(int id, int number, int width, int height, String applicationPath, String type) throws GetEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            if (width == 0) {
                width = 100;
            }
            if (height == 0) {
                height = 100;
            }
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            PictureEntity pictureEntity = productMapper.getBrandImage(id, number - 1);
            if (pictureEntity == null)
                return ImageHelper.getDefaultBrandImage(applicationPath, width, height, type);
            return ImageHelper.getImage(applicationPath, pictureEntity, width, height, "brands", type);
        } catch (Exception ex) {
            if (dbSession != null)
                dbSession.rollback();
            throw new GetEntityException(MessageFormat.format("Cannot get image for brand id: {0}; original message: {1}", id, ex.getMessage()));
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }

    //TODO: Refactor parameters
    public void create(ProductEntity product, List<File> images, File dataPath) throws CreateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            createAndMapBrand(product, productMapper);
            productMapper.create(product);
            mapProductToCategory(product, dbSession, productMapper);
            addImagesIfAny(product, images, dataPath, dbSession);
            dbSession.commit();
        } catch (Exception ex) {
            //TODO: remove picture from file system if something goes wrong
            if (dbSession != null)
                dbSession.rollback();
            throw new CreateEntityException(MessageFormat.format("Cannot create product: {0}; original message {1}", product.title, ex.getMessage()));
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }

    private void createAndMapBrand(ProductEntity product, ProductMapper productMapper) {
        if (product.brandId > 0)
            return;
        Integer brandId = productMapper.isBrandExists(product.brandName);
        if (brandId != null) {
            product.brandId = brandId;
        } else {
            BrandEntity brand = new BrandEntity(product.brandName);
            productMapper.createBrand(brand);
            product.brandId = brand.id;
        }
    }

    //TODO: Refactor parameters
    private void addImagesIfAny(ProductEntity product, List<File> images, File dataPath, SqlSession dbSession) throws IOException {
        if (images != null) {
            for (File imageFile : images) {
                if (imageFile == null)
                    continue;
                BufferedImage image = ImageIO.read(imageFile);
                addPicture(new PictureEntity(product.id, image, "jpg"), dataPath.toString(), dbSession);
            }
        }
    }

    private void mapProductToCategory(ProductEntity product, SqlSession dbSession, ProductMapper productMapper) {
        if (!StringHelper.isNullOrEmpty(product.categoryTitle)) {
            CategoryMapper categoryMapper = dbSession.getMapper(CategoryMapper.class);
            CategoryEntity category = categoryMapper.getByTitle(product.categoryTitle);
            if (category == null) {
                category = new CategoryEntity(product.categoryTitle);
                categoryMapper.create(category);
                dbSession.commit();
            }
            product.categoryId = category.id;
            productMapper.putInCategory(product);

        } else if (product.categoryId > 0) {
            productMapper.putInCategory(product);
        }
    }


    public int getImagesCount(int productId) throws GetEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            return productMapper.getImagesCount(productId);
        } catch (Exception ex) {
            throw new GetEntityException(MessageFormat.format("Cannot get images count for productId {0}; Original message: {1}", productId, ex.getMessage()));
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }

    public CategoryEntity getCategories() throws IOException {
        return getCategories(0);
    }

    public List<AutocompleteEntity> getBrandList(String q) throws IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            q = MessageFormat.format("%{0}%", q);
            List<AutocompleteEntity> brands = productMapper.searchBrandList(q);
            return brands;
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }

    public void create(ProductEntity productEntity) throws CreateEntityException, IOException {
        create(productEntity, null, null);
    }

    public boolean isProductExists(String title) throws IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            Integer id = productMapper.isProductExists(title.toUpperCase());
            return id != null;

        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public void updatePrice(ProductEntity productEntity) throws IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            productMapper.updatePrice(productEntity);
            dbSession.commit();
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public CategoryEntity getCategoryByTitle(String title) throws IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            return productMapper.getCategoryByTitle(title.toUpperCase());
        } finally {
            if (dbSession != null) {
                dbSession.close();
            }
        }
    }

    public int createBrand(BrandEntity brand) throws CreateEntityException, IOException {
        SqlSession dbSession = DBSession.openSession();
        try {
            ProductMapper productMapper = dbSession.getMapper(ProductMapper.class);
            productMapper.createBrand(brand);
            dbSession.commit();
            return brand.id;

        } catch (Exception ex) {
            if (dbSession != null)
                dbSession.rollback();
            throw new CreateEntityException(MessageFormat.format("Cannot create brand {0}; Original message: {1}", brand.title, ex.getMessage()));
        } finally {
            if (dbSession != null)
                dbSession.close();
        }
    }


}
